package com.cmw.socket.l7.client;

import com.cmw.socket.bean.ServerInfo;
import com.cmw.socket.constant.UDPConstants;
import com.cmw.socket.utils.ByteUtils;
import lombok.extern.slf4j.Slf4j;

import java.net.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * UDP 搜索类
 * @author chengmingwei
 * @date 2020-06-27 10:11
 */
@Slf4j
public class UDPSearcher {

    private static final int LISTEN_PORT = UDPConstants.PORT_CLIENT_RESPONSE;

    public static ServerInfo searchServer(int timeOut){
        log.info("UDPSearcher Started.");
        //成功收到回送的栅栏
        CountDownLatch receiveLatch = new CountDownLatch(1);
        Listener listener = null;
        try {
            listener = listen(receiveLatch);
            sendBroadcast();
            receiveLatch.await(timeOut, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //完成
        log.info("UDPSearcher Finished.");
        if(null == listener) return null;

        List<ServerInfo> devices = listener.getServerAndClose();
        if(null != devices && devices.size() > 0){
            return devices.get(0);
        }
        return null;
    }

    private static void sendBroadcast() throws Exception {
        log.info("UDPSearcher sendBroadcast started.");
        //作为搜索方，让系统自动分配端口
        DatagramSocket ds = new DatagramSocket();
        //构建一份请求数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        //头部
        byteBuffer.put(UDPConstants.HEADER);
        //CMD命令
        byteBuffer.putShort((short)1);
        //回送端口信息
        byteBuffer.putInt(LISTEN_PORT);
        //直接构建packet
        DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(),
                byteBuffer.position() + 1);
        // 广播地址
        requestPacket.setAddress(InetAddress.getByName("255.255.255.255"));
        //设置服务器端口
        requestPacket.setPort(UDPConstants.PORT_SERVER);
        //发送
        ds.send(requestPacket);
        ds.close();
        //完成
        log.info("UDPSearcher sendBroadcast finished.");
    }

    private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {
        log.info("UDPSearcher start listen.");
        CountDownLatch startDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTEN_PORT, startDownLatch, receiveLatch);
        listener.start();
        startDownLatch.await();
        return listener;
    }


    /**
     * UDP 数据报文监听类
     */
    private static class Listener extends Thread{

        private final int listenPort;

        private final CountDownLatch startDownLatch;

        private final CountDownLatch receiveDownLatch;

        private final List<ServerInfo> serverInfoList = new ArrayList<>();

        private final byte[] buffer = new byte[128];

        private final int minLen = UDPConstants.HEADER.length + 2 + 4;;

        private boolean done = false;

        private DatagramSocket ds = null;

        public Listener(int listenPort, CountDownLatch startDownLatch, CountDownLatch receiveDownLatch) {
            this.listenPort = listenPort;
            this.startDownLatch = startDownLatch;
            this.receiveDownLatch = receiveDownLatch;
        }

        @Override
        public void run() {
            super.run();
            //通知已启动
            startDownLatch.countDown();

            try {
                //监听回送端口
                ds = new DatagramSocket(listenPort);
                //构建接收实体
                DatagramPacket receivePack = new DatagramPacket(buffer, buffer.length);
                while (!done){
                    //接收
                    ds.receive(receivePack);
                    //打印接收到的信息与发送者的信息
                    //发送者的IP
                    String ip = receivePack.getAddress().getHostAddress();
                    int port = receivePack.getPort();
                    int dataLen = receivePack.getLength();
                    byte[] data = receivePack.getData();
                    boolean isValid = dataLen >= minLen && ByteUtils.startsWith(data, UDPConstants.HEADER);
                    log.info("UDPSearcher receive form ip : {},\t port ：{},\tdataValid : {}", ip, port, isValid);
                    if(!isValid){
                        //无效继续
                        continue;
                    }
                    ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, UDPConstants.HEADER.length, dataLen);
                    final short cmd = byteBuffer.getShort();
                    final int serverPort = byteBuffer.getInt();
                    if(cmd != 2 || serverPort <= 0){
                        log.error("UDPSearcher receive data err, cmd ：{}，\tserverPort ： {}",cmd, serverPort);
                        continue;
                    }
                    String sn = new String(buffer, minLen, dataLen - minLen);
                    ServerInfo info = new ServerInfo(sn,serverPort, ip);
                    serverInfoList.add(info);
                    //成功接收到一份
                    receiveDownLatch.countDown();;
                }
            } catch (Exception ignored) {
                log.error(ignored.getMessage());
            }finally {
                close();
            }
            log.info("UDPSearcher listener finished.");
        }

        private void close(){
            if(null == ds) return;
            ds.close();
            ds = null;
        }

        List<ServerInfo> getServerAndClose(){
            done = true;
            close();
            return serverInfoList;
        }
    }
}
